{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5c0122e65c053f38",
   "metadata": {},
   "source": [
    "# Asynchronous Shopping Assistant using LangGraph\n",
    "\n",
    "## Overview\n",
    "\n",
    "In this tutorial, we'll build an asynchronous shopping assistant that can search for products on Amazon while maintaining a conversation with the user. This use case demonstrates how to leverage Amazon Bedrock AgentCore's asynchronous capabilities with LangGraph to create a responsive agent that can perform time-consuming tasks in the background.\n",
    "\n",
    "### Use Case Details\n",
    "\n",
    "| Information         | Details                                                                      |\n",
    "|---------------------|------------------------------------------------------------------------------|\n",
    "| Use case type       | Shopping Assistant                                                           |\n",
    "| Agent type          | Asynchronous                                                                 |\n",
    "| Agentic Framework   | LangGraph                                                                    |\n",
    "| LLM model           | Anthropic Claude 3 Haiku                                                     |\n",
    "| Components          | AgentCore Runtime, Browser Tool, Async Tasks                                 |\n",
    "| Example complexity  | Intermediate                                                                 |\n",
    "| SDK used            | Amazon BedrockAgentCore Python SDK                                           |\n",
    "\n",
    "### Use Case Architecture\n",
    "\n",
    "This shopping assistant demonstrates a powerful pattern for building responsive AI agents that can handle time-consuming tasks without blocking the conversation flow:\n",
    "\n",
    "1. The user asks about products they're interested in\n",
    "2. The agent launches a background task using AgentCore's async capabilities\n",
    "3. While the background task runs, the agent can continue conversing with the user\n",
    "4. When results are ready, they're stored in a file that the agent can access\n",
    "5. The agent can retrieve and present the results when appropriate\n",
    "\n",
    "The architecture combines several key components:\n",
    "- **LangGraph**: For building the agent's workflow and decision-making process\n",
    "- **AgentCore Runtime**: For hosting and scaling the agent\n",
    "- **AgentCore Async Tasks**: For managing background operations\n",
    "- **Browser Tool**: For searching and extracting product information\n",
    "- **File System**: For storing and retrieving search results\n",
    "\n",
    "![](images/architecture_diagram.png)\n",
    "\n",
    "### Key Features\n",
    "\n",
    "* **Asynchronous Task Management**: Launch background tasks that don't block the conversation\n",
    "* **Browser Tool Integration**: Use AgentCore's Browser Tool to search and extract product information\n",
    "* **Stateful Conversations**: Maintain context across multiple turns using LangGraph\n",
    "* **File System Integration**: Store and retrieve search results using file system tools\n",
    "* **Parallel Processing**: Handle multiple product searches simultaneously\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a676f58ecf52b42",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "\n",
    "To execute this tutorial you will need:\n",
    "* Python 3.10+\n",
    "* AWS credentials with appropriate permissions\n",
    "* Amazon Bedrock AgentCore SDK\n",
    "* LangGraph\n",
    "* Docker running (for local testing and deployment)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "initial_id",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "!pip install bedrock-agentcore-sdk langgraph langchain langchain_aws typing_extensions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca924a7a2731e26f",
   "metadata": {},
   "source": [
    "## Understanding Asynchronous Agents\n",
    "\n",
    "Traditional conversational agents process user requests synchronously, which means they block the conversation until a task is complete. This approach works well for quick operations but creates a poor user experience for time-consuming tasks like web browsing or complex searches.\n",
    "\n",
    "Asynchronous agents solve this problem by:\n",
    "\n",
    "1. **Non-blocking operations**: Tasks run in the background while the conversation continues\n",
    "2. **Task management**: The agent can start, monitor, and retrieve results from background tasks\n",
    "3. **Improved responsiveness**: Users get immediate feedback and can continue interacting\n",
    "\n",
    "Amazon Bedrock AgentCore provides built-in support for asynchronous operations through its task management capabilities, which we'll leverage in this shopping assistant.\n",
    "\n",
    "### The Shopping Assistant Use Case\n",
    "\n",
    "Our shopping assistant will:\n",
    "- Accept product search queries from users\n",
    "- Launch background tasks to search Amazon.com using the Browser Tool\n",
    "- Continue conversing with the user while searches run\n",
    "- Store search results in files when complete\n",
    "- Retrieve and present results when the user asks for them\n",
    "\n",
    "This pattern is particularly valuable for e-commerce applications where product searches can take time, but users expect responsive interactions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "intro-to-entrypoint",
   "metadata": {},
   "source": [
    "## Understanding the Asynchronous Shopping Assistant Code\n",
    "\n",
    "Let's examine the key components of our asynchronous shopping assistant implementation. The code below implements a shopping assistant that can search for products on Amazon while maintaining a conversation with the user.\n",
    "\n",
    "**Key Components:**\n",
    "\n",
    "1. **AgentCore Integration**: Initializes the BedrockAgentCoreApp that hosts our agent\n",
    "2. **Browser Tool Implementation**: Uses NovaAct to control a browser for product searches\n",
    "3. **Asynchronous Task Management**: Manages background tasks that don't block the conversation\n",
    "4. **LangGraph Agent**: Defines the agent's workflow and decision-making process\n",
    "5. **Entrypoint Function**: Handles agent invocation with streaming support\n",
    "\n",
    "Below is the complete implementation of our asynchronous shopping assistant:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "pygmentize-cell",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Display the entrypoint code with syntax highlighting\n",
    "!pygmentize async_example_langgraph_browser_tool.py"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-cell",
   "metadata": {},
   "source": [
    "### Understanding the Entrypoint File\n",
    "\n",
    "The `async_example_langgraph_browser_tool.py` file contains the complete implementation of our asynchronous shopping assistant. Let's explore its key components:\n",
    "\n",
    "1. **AgentCore Integration**:\n",
    "   ```python\n",
    "   from bedrock_agentcore.runtime import BedrockAgentCoreApp\n",
    "   app = BedrockAgentCoreApp()\n",
    "   ```\n",
    "   This initializes the AgentCore application that will host our agent.\n",
    "\n",
    "2. **Browser Tool Implementation**:\n",
    "   ```python\n",
    "   def _run_browser_task(request: str):\n",
    "       with browser_session('us-west-2') as client:\n",
    "           # Browser session setup\n",
    "           # ...\n",
    "           task_id = app.add_async_task(\"using_browser_tool\")\n",
    "           # ...\n",
    "           with NovaAct(...) as nova_act:\n",
    "               result = nova_act.act(prompt=request, max_steps=5)\n",
    "               # ...\n",
    "           success = app.complete_async_task(task_id)\n",
    "   ```\n",
    "   This function runs a browser task in the background using NovaAct and AgentCore's async task management.\n",
    "\n",
    "3. **LangGraph Agent**:\n",
    "   ```python\n",
    "   graph_builder = StateGraph(State)\n",
    "   graph_builder.add_node(\"chatbot\", chatbot)\n",
    "   graph_builder.add_node(\"tools\", tool_node)\n",
    "   # ...\n",
    "   graph = graph_builder.compile(checkpointer=checkpointer, store=in_memory_store)\n",
    "   ```\n",
    "   This creates a LangGraph agent with a chatbot node and tools node.\n",
    "\n",
    "4. **Asynchronous Entrypoint**:\n",
    "   ```python\n",
    "   @app.entrypoint\n",
    "   async def agent_invocation(payload, context):\n",
    "       # ...\n",
    "       async for chunk in graph.astream(tmp_msg, stream_mode=\"updates\", config=config):\n",
    "           # Process and yield chunks\n",
    "   ```\n",
    "   This function is the entrypoint for our agent, decorated with `@app.entrypoint`. It uses async/await patterns to stream responses.\n",
    "\n",
    "The file demonstrates how to build an asynchronous agent that can perform background tasks while maintaining a conversation with the user. It leverages AgentCore's task management capabilities and LangGraph's state management to create a responsive shopping assistant."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0861401-a111-4ade-9e02-50f52fdfa9b1",
   "metadata": {},
   "source": [
    "### Creating a requirements.txt file\n",
    "\n",
    "Next, we need to create a requirements.txt file that lists all the dependencies our agent needs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54dd2fdf-985c-4a70-8b87-071783a209de",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile requirements.txt\n",
    "bedrock-agentcore-sdk\n",
    "langgraph\n",
    "langchain\n",
    "langchain_aws\n",
    "typing_extensions\n",
    "langchain_community"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8855aceb-b79f-4aaa-b16f-8577c059816a",
   "metadata": {},
   "source": [
    "### Creating runtime role\n",
    "\n",
    "Before deploying our agent, we need to create an IAM role that will be used by AgentCore Runtime. This role will have the necessary permissions to access Amazon Bedrock and other AWS services."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e79eba2-ca59-463f-9ebf-56e362d7ae66",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "import json\n",
    "import boto3\n",
    "\n",
    "# Get the current notebook's directory\n",
    "current_dir = os.path.dirname(os.path.abspath('__file__' if '__file__' in globals() else '.'))\n",
    "\n",
    "# Navigate up to the utils.py location\n",
    "utils_dir = os.path.join(current_dir, '..', '..')\n",
    "utils_dir = os.path.abspath(utils_dir)\n",
    "\n",
    "# Add to sys.path\n",
    "sys.path.insert(0, utils_dir)\n",
    "\n",
    "from utils import create_agentcore_role\n",
    "\n",
    "agent_name=\"async_shopping_assistant\"\n",
    "agentcore_iam_role = create_agentcore_role(agent_name=agent_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e1b84cc-798e-472c-ac0b-2c315f4b704d",
   "metadata": {},
   "source": [
    "### Configure AgentCore Runtime deployment\n",
    "\n",
    "Now we'll use the AgentCore starter toolkit to configure the deployment of our agent. This will generate a Docker file based on our application code and prepare everything for deployment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17a32ab8-7701-4900-8055-e24364bdf35c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bedrock_agentcore_starter_toolkit import Runtime\n",
    "from boto3.session import Session\n",
    "import time\n",
    "\n",
    "boto_session = Session()\n",
    "region = boto_session.region_name\n",
    "print(f\"Using AWS region: {region}\")\n",
    "\n",
    "agentcore_runtime = Runtime()\n",
    "\n",
    "response = agentcore_runtime.configure(\n",
    "    entrypoint=\"async_example_langgraph_browser_tool.py\",\n",
    "    execution_role=agentcore_iam_role['Role']['Arn'],\n",
    "    auto_create_ecr=True,\n",
    "    requirements_file=\"requirements.txt\",\n",
    "    region=region\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0ae9c09-09db-4a76-871a-92eacd96b9c3",
   "metadata": {},
   "source": [
    "### Launching agent to AgentCore Runtime\n",
    "\n",
    "Now that we've configured our agent, let's launch it to the AgentCore Runtime. This will create an Amazon ECR repository and deploy our agent to AgentCore Runtime.\n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/launch.png\" width=\"75%\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "afa6ac09-9adb-4846-9fc1-4d12aeb74853",
   "metadata": {},
   "outputs": [],
   "source": [
    "launch_result = agentcore_runtime.launch()\n",
    "launch_result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7f89c56-918a-4cab-beaa-c7ac43a2ba29",
   "metadata": {},
   "source": [
    "### Checking for the AgentCore Runtime Status\n",
    "\n",
    "Let's check the deployment status of our agent. We'll wait until the status is \"READY\" before proceeding."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d909e42-e1a0-407f-84c2-3d16cc889cd3",
   "metadata": {},
   "outputs": [],
   "source": [
    "status_response = agentcore_runtime.status()\n",
    "status = status_response.endpoint['status']\n",
    "end_status = ['READY', 'CREATE_FAILED', 'DELETE_FAILED', 'UPDATE_FAILED']\n",
    "while status not in end_status:\n",
    "    time.sleep(10)\n",
    "    status_response = agentcore_runtime.status()\n",
    "    status = status_response.endpoint['status']\n",
    "    print(status)\n",
    "status"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fefa09f2-d25a-483f-aedb-11690bb8923a",
   "metadata": {},
   "source": [
    "### Invoking AgentCore Runtime\n",
    "\n",
    "Now that our agent is deployed and ready, let's invoke it with a product search query. This will demonstrate the asynchronous capabilities of our shopping assistant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11249103-cfb3-47b5-970d-981a977a225a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Markdown, display\n",
    "\n",
    "# Invoke the agent with a product search query\n",
    "invoke_response = agentcore_runtime.invoke({\"prompt\": \"I'm looking for a good laptop for programming. Can you help me find some options?\"})\n",
    "\n",
    "# Display the response\n",
    "response_text = json.loads(invoke_response['response'][0].decode(\"utf-8\"))\n",
    "display(Markdown(response_text))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c1d2bce-be41-478c-8bed-b4037c385795",
   "metadata": {},
   "source": [
    "### Viewing the Asynchronous Shopping Assistant in Action\n",
    "\n",
    "When you invoke the agent with a product search query, it will launch a background task to search for products while continuing to chat with you. To see this in action:\n",
    "\n",
    "1. Navigate to the AWS Console and go to the Amazon Bedrock section\n",
    "![](images/go_to_bedrock_agentcore_console.png)\n",
    "\n",
    "\n",
    "2. In the Browser Tool tab, you'll see your agent's browser sessions\n",
    "   ![](images/browser_use_tab.png)\n",
    "\n",
    "3. You can see the built-in browser sandbox that AgentCore provides\n",
    "   ![](images/aws_built_in_browser_sandbox.png)\n",
    "\n",
    "4. Click on \"View Live Session\" to see the browser in action\n",
    "   ![](images/click_view_live_session.png)\n",
    "\n",
    "5. Watch as the agent interacts with the browser to search for products\n",
    "   ![](images/watch_the_agent_interact_with_browser.png)\n",
    "\n",
    "You'll notice that the agent is able to continue conversing with you while the product search is running in the background. When the search is complete, you can ask the agent to show you the results.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f84e68d-6c04-41b9-bf5b-60edc3fa0985",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ask a follow-up question\n",
    "invoke_response = agentcore_runtime.invoke({\"prompt\": \"While you're searching, can you tell me what features I should look for in a programming laptop?\"})\n",
    "\n",
    "# Display the response\n",
    "response_text = json.loads(invoke_response['response'][0].decode(\"utf-8\"))\n",
    "display(Markdown(response_text))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d3fdfe404469632",
   "metadata": {},
   "source": [
    "Now, let's check if the search results are ready:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "08f86824-c775-4ad4-aaee-f18e8cf390b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Wait a moment to allow the background task to complete\n",
    "time.sleep(10)\n",
    "\n",
    "# Ask for the search results\n",
    "invoke_response = agentcore_runtime.invoke({\"prompt\": \"Can you show me the laptop search results now?\"})\n",
    "\n",
    "# Display the response\n",
    "response_text = json.loads(invoke_response['response'][0].decode(\"utf-8\"))\n",
    "display(Markdown(response_text))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b118ad38-feeb-4d1d-9d57-e5c845becc56",
   "metadata": {},
   "source": [
    "### Using boto3 to Invoke the Agent\n",
    "\n",
    "You can also use boto3 to invoke your agent programmatically. This is useful for integrating your agent into other applications."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76a6cf1416830a54",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent_arn = launch_result.agent_arn\n",
    "agentcore_client = boto3.client(\n",
    "    'bedrock-agentcore',\n",
    "    region_name=region\n",
    ")\n",
    "\n",
    "boto3_response = agentcore_client.invoke_agent_runtime(\n",
    "    agentRuntimeArn=agent_arn,\n",
    "    qualifier=\"DEFAULT\",\n",
    "    payload=json.dumps({\"prompt\": \"Can you search for wireless headphones?\"})\n",
    ")\n",
    "\n",
    "if \"text/event-stream\" in boto3_response.get(\"contentType\", \"\"):\n",
    "    content = []\n",
    "    for line in boto3_response[\"response\"].iter_lines(chunk_size=1):\n",
    "        if line:\n",
    "            line = line.decode(\"utf-8\")\n",
    "            if line.startswith(\"data: \"):\n",
    "                line = line[6:]\n",
    "                print(line)\n",
    "                content.append(line)\n",
    "    display(Markdown(\"\\n\".join(content)))\n",
    "else:\n",
    "    try:\n",
    "        events = []\n",
    "        for event in boto3_response.get(\"response\", []):\n",
    "            events.append(event)\n",
    "    except Exception as e:\n",
    "        events = [f\"Error reading EventStream: {e}\"]\n",
    "    display(Markdown(json.loads(events[0].decode(\"utf-8\"))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7d386ab54e85e63",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "In this tutorial, we've built an asynchronous shopping assistant using LangGraph and Amazon Bedrock AgentCore. Our agent can:\n",
    "\n",
    "1. Accept product search queries from users\n",
    "2. Launch background tasks to search for products\n",
    "3. Continue conversing with the user while searches run\n",
    "4. Store search results in files when complete\n",
    "5. Retrieve and present results when the user asks for them\n",
    "\n",
    "This pattern is particularly valuable for applications where tasks can take time to complete, but users expect responsive interactions. By leveraging AgentCore's asynchronous capabilities, we can create agents that provide a much better user experience.\n",
    "\n",
    "### Key Takeaways\n",
    "\n",
    "- **Asynchronous agents** provide a better user experience for time-consuming tasks\n",
    "- **AgentCore Runtime** makes it easy to deploy and scale agents\n",
    "- **Browser Tool** enables agents to search and extract information from websites\n",
    "- **LangGraph** provides a flexible framework for building complex agent workflows\n",
    "- **File System Integration** allows agents to store and retrieve results\n",
    "\n",
    "### Next Steps\n",
    "\n",
    "To further enhance this shopping assistant, you could:\n",
    "\n",
    "1. Add product comparison capabilities\n",
    "2. Implement user preferences and personalization\n",
    "3. Add support for multiple search providers\n",
    "4. Integrate with e-commerce APIs for real-time pricing and availability\n",
    "5. Implement memory to remember user preferences across sessions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68499675-db8d-47c6-8c0c-5d66dcb06229",
   "metadata": {},
   "source": [
    "## Cleanup (Optional)\n",
    "\n",
    "If you want to clean up the resources created in this tutorial, you can use the following code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "932110e6-fca6-47b6-b7c5-c4714a866a80",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get the agent ID and ECR URI\n",
    "launch_result.ecr_uri, launch_result.agent_id, launch_result.ecr_uri.split('/')[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1226d59e6b56c96",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize clients\n",
    "agentcore_control_client = boto3.client(\n",
    "    'bedrock-agentcore-control',\n",
    "    region_name=region\n",
    ")\n",
    "ecr_client = boto3.client(\n",
    "    'ecr',\n",
    "    region_name=region\n",
    "    \n",
    ")\n",
    "iam_client = boto3.client('iam')\n",
    "\n",
    "# Delete the agent runtime\n",
    "runtime_delete_response = agentcore_control_client.delete_agent_runtime(\n",
    "    agentRuntimeId=launch_result.agent_id\n",
    ")\n",
    "\n",
    "# Delete the ECR repository\n",
    "response = ecr_client.delete_repository(\n",
    "    repositoryName=launch_result.ecr_uri.split('/')[1],\n",
    "    force=True\n",
    ")\n",
    "\n",
    "# Delete the IAM role\n",
    "policies = iam_client.list_role_policies(\n",
    "    RoleName=agentcore_iam_role['Role']['RoleName'],\n",
    "    MaxItems=100\n",
    ")\n",
    "\n",
    "for policy_name in policies['PolicyNames']:\n",
    "    iam_client.delete_role_policy(\n",
    "        RoleName=agentcore_iam_role['Role']['RoleName'],\n",
    "        PolicyName=policy_name\n",
    "    )\n",
    "iam_response = iam_client.delete_role(\n",
    "    RoleName=agentcore_iam_role['Role']['RoleName']\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
